package com.bpzj.util.ssh;

import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class AutoDeploy {

  private static final String[] requireKeys = {"host", "username", "password", "localFile", "remotePath"};
  private static final String[] notRequireKeys = {"port", "command"};
  static StringBuilder missingKey = new StringBuilder();
  static boolean needRecordStdOut = true;
  static StringBuffer shellOutBuffer;

  public static void main(String[] arg) {
    HashMap<String,String> argMap = setDeployArgs(arg);
    try {
      List<ExpectInput> expectInputList = getExpectInput(arg);

      SSHClient ssh = new SSHClient();
      ssh.addHostKeyVerifier(new PromiscuousVerifier());
      ssh.connect(argMap.get("host"), Integer.parseInt(argMap.getOrDefault("port", "22")));
      ssh.authPassword(argMap.get("username"), argMap.get("password"));

      // SFTPClient sftp = ssh.newSFTPClient();
      // sftp.put(deployArg.localFile, deployArg.remotePath);
      // sftp.close();

      Session session = ssh.startSession();
      if (expectInputList.isEmpty()) {
        // 如果没有需要手动选择的跳板机， 直接执行命令即可，
        Session.Command cmd = session.exec(argMap.getOrDefault("command", "ls"));
        String ret = IOUtils.readFully(cmd.getInputStream()).toString();
        System.out.println(ret);
        return;
      }

      // 如果类似堡垒机的, 需要手动选择, 使用下面的方法, 根据正则表达式自动选择

      shellOutBuffer = new StringBuffer(8192);
      session.allocateDefaultPTY();
      Session.Shell shell = session.startShell();
      new Thread(new SysOutThread(shell.getInputStream())).start();

      OutputStream output = shell.getOutputStream();
      // 按自定义的正则输入字符 todo 抽象出  expect() 函数
      for (ExpectInput expectInput : expectInputList) {
        expectStdOut(Pattern.compile(expectInput.expect, Pattern.DOTALL));
        String input = findInputFromExceptBefore(expectInput);
        output.write(input.getBytes());
        output.flush();
      }

      if (argMap.get("command") != null) {
        output.write((argMap.get("command") + "\n").getBytes());
        output.flush();
      }

      // Now make System.in act as stdin. To exit, hit Ctrl+D (since that results in an EOF on System.in)
      // This is kinda messy because java only allows console input after you hit return
      // But this is just an example... a GUI app could implement a proper PTY
      new StreamCopier(System.in, output, LoggerFactory.DEFAULT)
          .bufSize(shell.getRemoteMaxPacketSize())
          .copy();

      session.close();
      ssh.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * TODO 添加超时参数， 超时后退出循环
   */
  private static void expectStdOut(Pattern pattern) throws InterruptedException {
    int size = 0;
    while (true) {
      // 和上个列表一样，sleep 1 s
      if (size == shellOutBuffer.length()) {
        Thread.sleep(500);
        continue;
      }

      size = shellOutBuffer.length();
      if (pattern.matcher(shellOutBuffer).matches()) {
        Thread.sleep(500);
        return;
      }
    }
  }


  private static String findInputFromBeforeByPattern(Pattern pattern) {
    Matcher matcher = pattern.matcher(shellOutBuffer);
    if (matcher.matches()) {
      String input = matcher.group(1) + "\n";
      shellOutBuffer.delete(0, shellOutBuffer.length());
      return input;
    } else {
      throw new IllegalArgumentException("未找到符合" + pattern.toString() + "的字符");
    }
  }


  private static String findInputFromExceptBefore(ExpectInput expectInput) {
    Pattern expectHasGroup = Pattern.compile(".*\\(.*\\).*", Pattern.DOTALL);
    if (expectHasGroup.matcher(expectInput.expect).matches()) {
      return findInputFromBeforeByPattern(Pattern.compile(expectInput.expect, Pattern.DOTALL));
    } else if (expectHasGroup.matcher(expectInput.input).matches()) {
      return findInputFromBeforeByPattern(Pattern.compile(expectInput.expect, Pattern.DOTALL));
    } else {
      shellOutBuffer.delete(0, shellOutBuffer.length());
      return expectInput.input.contains("\n") ? expectInput.input : expectInput.input + "\n";
    }
  }


  private static List<ExpectInput> getExpectInput(String[] arg) {
    List<ExpectInput> expectInputList = new ArrayList<>();
    for (String s : arg) {
      int expectIndexInString, inputIdxInMainArgs;
      if (s.startsWith("expect") && (expectIndexInString = s.indexOf("=")) >= 0) {
        ExpectInput expectInput = new ExpectInput();
        expectInput.expect = s.substring(expectIndexInString + 1);
        String groupNum = s.substring(6, expectIndexInString);
        String inputKey = "input" + groupNum + "=";
        if ((inputIdxInMainArgs = indexOf(arg, inputKey)) >= 0) {
          expectInput.input = arg[inputIdxInMainArgs].substring(inputKey.length());
        }
        expectInputList.add(expectInput);
      }
    }
    return expectInputList;
  }


  /**
   * 解析命令行参数， 得到需要的数据
   * @return
   */
  private static HashMap<String, String> setDeployArgs(String[] arg) throws IllegalArgumentException {
    if (arg == null || arg.length == 0) {
      throw new IllegalArgumentException("参数为空");
    }
    HashMap<String, String> argsMap = new HashMap<>();
    for (String key : requireKeys) {
      int i = indexOf(arg, key + "=");
      if (i < 0) {
        missingKey.append(key).append(",");
      } else {
        argsMap.put(key, arg[i].substring(key.length() + 1));
      }
    }
    for (String key : notRequireKeys) {
      int i = indexOf(arg, key + "=");
      if (i > 0) {
        argsMap.put(key, arg[i].substring(key.length() + 1));
      }
    }
    if (missingKey.length() != 0) {
      throw new IllegalArgumentException("缺少参数" + missingKey.substring(0, missingKey.length() - 1));
    }
    return argsMap;
  }

  private static int indexOf(String[] arg, String key) {
    for (int i = 0; i < arg.length; i++) {
      if (arg[i].startsWith(key)) {
        return i;
      }
    }
    return -1;
  }

  /**
   * shell 输出线程, 把shell的信息输出到 System.out
   */
  static class SysOutThread implements Runnable {

    InputStream input;

    public SysOutThread(InputStream input) {
      this.input = input;
    }

    @Override
    public void run() {
      try {
        final byte[] buffer = new byte[8192];
        int len = -1;
        while ((len = input.read(buffer)) != -1) { // 当等于-1说明没有数据可以读取了
          System.out.write(buffer, 0, len);   // 把读取到的内容写到输出流中
          if (needRecordStdOut) {
            shellOutBuffer.append(new String(buffer, 0, len));
          }
        }
      } catch (IOException ignored) {
      }
    }
  }

  static class ExpectInput {
    String expect;
    String input;
  }
}
